//Variable Declarations
CAF = {}

CAF.StartingUp = false;

local DEBUG = true

local Addons = {}

local addonlevel = {}
addonlevel["1"] = {}
addonlevel["2"] = {}
addonlevel["3"] = {}
addonlevel["4"] = {}
addonlevel["5"] = {}

local hooks = {}
hooks["think"] = {}
hooks["think2"] = {}
hooks["think3"] = {}
hooks["OnEntitySpawn"] = {}
hooks["OnAddonDestruct"] = {}
hooks["OnAddonConstruct"] = {}
hooks["OnAddonExtraOptionChange"] = {}

local function ErrorOffStuff(String)
	Msg("----------------------------------------------------------------------\n")
	Msg("-----------Custom Addon Management Framework Error----------\n")
	Msg("----------------------------------------------------------------------\n")
	Msg(tostring(String).."\n")
end

AddCSLuaFile("autorun/client/cl_caf_autostart.lua")

if (!sql.TableExists("CAF_AddonStatus")) then

	sql.Query("CREATE TABLE IF NOT EXISTS CAF_AddonStatus ( id VARCHAR(50) PRIMARY KEY , status TINYINT(1));")

end



//function Declarations

//Local functions

local function UpdateAddonStatus(addon, status)
	if not addon or not status then return false, "Missing parameter(s)" end
	local id = sql.SQLStr(addon)
	local stat = sql.SQLStr(status)
	sql.Query("UPDATE CAF_AddonStatus SET status="..stat.." WHERE id="..id..";")
end

local function SaveAddonStatus(addon, status)
	if not addon or not status then return false, "Missing parameter(s)" end
	local id = sql.SQLStr(addon)
	local stat = sql.SQLStr(status)
	local data = sql.Query("INSERT INTO CAF_AddonStatus(id, status) VALUES("..id..", "..stat..");")
	if data then 
		Msg("Error making a profile for "..ply:Nick().."\n"..data.."\n") 
	end
end

local function LoadAddonStatus( addon )
	if not addon then return false, "No Addon Given" end
	local id = sql.SQLStr(addon)
	local data = sql.Query("SELECT * FROM CAF_AddonStatus WHERE id = "..id..";")
	if (!data) then
		SaveAddonStatus(addon, 1)
	else
		return util.tobool(data[1]["status"])
	end
	return true
end



local function Think()
	for k , v in pairs(hooks["think"]) do
		local ok, err = pcall(v)
		if not (ok) then
			CAF.WriteToDebugFile("CAF_Hooks", "Think Error: " ..err .."\n")
		end
	end
end

local function Think2()
	for k , v in pairs(hooks["think2"]) do
		local ok, err = pcall(v)
		if not (ok) then
			CAF.WriteToDebugFile("CAF_Hooks", "Think2 Error: " ..err .."\n")
		end
	end
end


local function Think3()
	for k , v in pairs(hooks["think3"]) do
		local ok, err = pcall(v)
		if not (ok) then
			CAF.WriteToDebugFile("CAF_Hooks", "Think3 Error: " ..err .."\n")
		end
	end
end

local function OnEntitySpawn(ent , enttype , ply)
	for k , v in pairs(hooks["OnEntitySpawn"]) do
		local ok, err = pcall(v, ent , enttype , ply)
		if not (ok) then
			CAF.WriteToDebugFile("CAF_Hooks", "OnEntitySpawn Error: " .. err .."\n")
		end
	end
end

local function  OnAddonDestruct(name)
	if not name then return end
	umsg.Start("CAF_Addon_Destruct")
		umsg.String(name)
	umsg.End()
	if not CAF.StartingUp then
		for k , v in pairs(hooks["OnAddonDestruct"]) do
			local ok, err = pcall(v, name)
			if not (ok) then
				CAF.WriteToDebugFile("CAF_Hooks", "OnAddonDestruct Error: " .. err .. "\n")
			end
		end
	end
end

local function OnAddonConstruct(name)
	if not name then return end
	//for k, ply in pairs(player.GetAll( )) do
		umsg.Start("CAF_Addon_Construct")//, ply
			umsg.String(name)
		umsg.End()
	//end
	if not CAF.StartingUp then
		for k , v in pairs(hooks["OnAddonConstruct"]) do
			local ok, err = pcall(v, name)
			if not (ok) then
				CAF.WriteToDebugFile("CAF_Hooks", "OnAddonConstruct Error: " .. err .. "\n")
			end
		end
	end
end

local function OnAddonExtraOptionChange(AddonName, OptionName, NewStatus)
	if not AddonName or not OptionName then return nil, "Missing Argument" end
	for k , v in pairs(hooks["OnAddonExtraOptionChange"]) do
		local ok, err = pcall(v, AddonName, OptionName, NewStatus)
		if not (ok) then
			CAF.WriteToDebugFile("CAF_Hooks", "OnAddonExtraOptionChange Error: " .. err .. "\n")
		end
	end
end

//Gmod Spawn Hooks

local function SpawnedSent( ply , ent )
	//Msg("Sent Spawned\n")
	OnEntitySpawn(ent , "SENT" , ply)
end

local function SpawnedVehicle( ply , ent)
	//Msg("Vehicle Spawned\n")
	OnEntitySpawn(ent , "VEHICLE" , ply)
end	

local function SpawnedEnt( ply , model , ent )
	//Msg("Prop Spawned\n")
	OnEntitySpawn(ent , "PROP" , ply)
end

local function PlayerSpawn(ply)
	//Msg("Prop Spawned\n")
	OnEntitySpawn(ply , "PLAYER" , ply)
end

local function NPCSpawn(ply, ent)
	//Msg("Prop Spawned\n")
	OnEntitySpawn(ent , "NPC" , ply)
end
hook.Add( "PlayerSpawnedNPC", "CAF NPC Spawn", NPCSpawn )
hook.Add( "PlayerInitialSpawn", "CAF PLAYER Spawn", PlayerSpawn )
hook.Add( "PlayerSpawnedProp", "CAF PROP Spawn", SpawnedEnt )
hook.Add( "PlayerSpawnedSENT", "CAF SENT Spawn", SpawnedSent )
hook.Add( "PlayerSpawnedVehicle", "CAF VEHICLE Spawn", SpawnedVehicle )

//Global function
--[[

]]

--[[
	WriteToDebugFile
	This function will write the selected message to 
		1) the console
		2) the specified file into the CAF_DEBUG/Server/ folder
			If the file doesn't exist it will be created
]]
function CAF.WriteToDebugFile(filename, message)
	if not filename or not message then return nil , "Missing Argument" end
	if DEBUG then
		ErrorNoHalt("Filename: "..tostring(filename)..", Message: "..tostring(message).."\n")
	end
	local contents = file.Read("CAF_Debug/server/"..filename..".txt") 
	contents = contents or "" 
	contents = contents .. message
	file.Write("CAF_Debug/server/"..filename..".txt", contents)
end

--[[
	ClearDebugFile
		This function will clear the given file in the debug folder
		It will return the content that was in the file before it got cleared
]]
function CAF.ClearDebugFile(filename)
	if not filename then return nil , "Missing Argument" end
	local contents = file.Read("CAF_Debug/server/"..filename..".txt") 
	contents = contents or "" 
	file.Write("CAF_Debug/server/"..filename..".txt", "")
	return content
end

--[[
	GetSavedAddonStatus
		This function will return the the status that was stored in the SQL file last time to make it easier so admins won't need to disable Addons again every time.
]]
function CAF.GetSavedAddonStatus( addon )
	return LoadAddonStatus( addon )
end

--[[
	Start
		This function loads all the Custom Addons on Startup
]]
function CAF.Start()
	Msg("Starting CAF Addons\n");
	CAF.StartingUp = true
	umsg.Start("CAF_Start_true")
	umsg.End()
	for k, v in pairs(addonlevel["1"]) do
		Msg("Addon1 " .. v .."\n")
		if Addons[v] then
			local ok, err = pcall(Addons[v].AddResourcesToSend)
			if not ok then
				CAF.WriteToDebugFile("CAF_ResourceSend", "AddResourcesToSend Error: " .. err .. "\n")
			end
		end
		if CAF.GetSavedAddonStatus(v) then
			if Addons[v] and Addons[v].GetStatus  and not Addons[v].GetStatus() then
				local ok , err = pcall(Addons[v].__Construct)
				if not ok then
					CAF.WriteToDebugFile("CAF_Construct", "Couldn't call constructor for "..v .. ": " .. err .. "\n")
				else
					OnAddonConstruct(v)
					Msg("Loaded addon: " .. v.."\n")
				end
			end
		end
	end
	for k, v in pairs(addonlevel["2"]) do
		if Addons[v] then
			local ok, err = pcall(Addons[v].AddResourcesToSend)
			if not ok then
				CAF.WriteToDebugFile("CAF_ResourceSend", "AddResourcesToSend Error: " .. err .. "\n")
			end
		end
		if CAF.GetSavedAddonStatus(v) then
			if Addons[v] and Addons[v].GetStatus  and not Addons[v].GetStatus() then
				local ok = true
				if Addons[v].GetRequiredAddons and Addons[v].GetRequiredAddons() then
					for l, w in pairs(Addons[v].GetRequiredAddons()) do
						if not Addons[w] then
							ok = false
						end
					end
				end
				if ok then
					local ok2 , err = pcall(Addons[v].__Construct)
					if not ok2 then
						CAF.WriteToDebugFile("CAF_Construct", "Couldn't call constructor for "..v .. ": " .. err .. "\n")
					else
						OnAddonConstruct(v)
						Msg("Loaded addon: " .. v.."\n")
					end
				end
			end
		end
	end
	for k, v in pairs(addonlevel["3"]) do
		if Addons[v] then
			local ok, err = pcall(Addons[v].AddResourcesToSend)
			if not ok then
				CAF.WriteToDebugFile("CAF_ResourceSend", "AddResourcesToSend Error: " .. err .. "\n")
			end
		end
		if CAF.GetSavedAddonStatus(v) then
			if Addons[v] and Addons[v].GetStatus  and not Addons[v].GetStatus() then
				local ok = true
				if Addons[v].GetRequiredAddons and Addons[v].GetRequiredAddons() then
					for l, w in pairs(Addons[v].GetRequiredAddons()) do
						if not Addons[w] then
							ok = false
						end
					end
				end
				if ok then
					local ok2 , err = pcall(Addons[v].__Construct)
					if not ok2 then
						CAF.WriteToDebugFile("CAF_Construct", "Couldn't call constructor for "..v .. ": " .. err .. "\n")
					else
						OnAddonConstruct(v)
						Msg("Loaded addon: " .. v.."\n")
					end
				end
			end
		end
	end
	for k, v in pairs(addonlevel["4"]) do
		if Addons[v] then
			local ok, err = pcall(Addons[v].AddResourcesToSend)
			if not ok then
				CAF.WriteToDebugFile("CAF_ResourceSend", "AddResourcesToSend Error: " .. err .. "\n")
			end
		end
		if CAF.GetSavedAddonStatus(v) then
			if Addons[v] and Addons[v].GetStatus  and not Addons[v].GetStatus() then
				local ok = true
				if Addons[v].GetRequiredAddons and Addons[v].GetRequiredAddons() then
					for l, w in pairs(Addons[v].GetRequiredAddons()) do
						if not Addons[w] then
							ok = false
						end
					end
				end
				if ok then
					local ok2 , err = pcall(Addons[v].__Construct)
					if not ok2 then
						CAF.WriteToDebugFile("CAF_Construct", "Couldn't call constructor for "..v .. ": " .. err .. "\n")
					else
						OnAddonConstruct(v)
						Msg("Loaded addon: " .. v.."\n")
					end
				end
			end
		end
	end
	for k, v in pairs(addonlevel["5"]) do
		if Addons[v] then
			local ok, err = pcall(Addons[v].AddResourcesToSend)
			if not ok then
				CAF.WriteToDebugFile("CAF_ResourceSend", "AddResourcesToSend Error: " .. err .. "\n")
			end
		end
		if CAF.GetSavedAddonStatus(v) then
			if Addons[v] and Addons[v].GetStatus  and not Addons[v].GetStatus() then
				local ok = true
				if Addons[v].GetRequiredAddons and Addons[v].GetRequiredAddons() then
					for l, w in pairs(Addons[v].GetRequiredAddons()) do
						if not Addons[w] then
							ok = false
						end
					end
				end
				if ok then
					local ok2 , err = pcall(Addons[v].__Construct)
					if not ok2 then
						CAF.WriteToDebugFile("CAF_Construct", "Couldn't call constructor for "..v .. ": " .. err .. "\n")
					else
						OnAddonConstruct(v)
						Msg("Loaded addon: ".. v.."\n")
					end
				end
			end
		end
	end
	CAF.StartingUp = false
	umsg.Start("CAF_Start_false")
	umsg.End()
end
hook.Add( "InitPostEntity", "CAF_Start", CAF.Start)

--[[
	This function will call the destruct function of an addon  and return if it's was succesfull or not (+ the errormessage)
]]
function CAF.Destruct(addon)
	if not addon then return false, "No Addon Name Given" end
	if not Addons[addon] then return false, "No Addon Registered With This Name" end
	local ok, mes = Addons[addon].__Destruct()
	if ok then
		OnAddonDestruct(addon)
		UpdateAddonStatus(addon, 0)
	end
	return ok, mes
end

--[[
	This function will call the construct function of an addon  and return if it's was succesfull or not (+ the errormessage)
]]
function CAF.Construct(addon)
	if not addon then return end
	if not Addons[addon] then return end
	local ok, mes = Addons[addon].__Construct()
	if ok then
		OnAddonConstruct(addon)
		UpdateAddonStatus(addon, 1)
	end
	return ok, mes
end

--[[
	This function will receive the construct info from the clientside VGUI menu
]]
local function AddonConstruct(ply, com, args)
	if not ply:IsAdmin() then ply:ChatPrint("You are not allowed to Construct a Custom Addon") return end
	if not args then ply:ChatPrint("You forgot to provide arguments") return end
	if not args[1] then ply:ChatPrint("You forgot to enter the Addon Name") return end
	if table.Count(args) > 1 then //Construct the Addon name if it had spaces in it
		for k , v in pairs(args) do
			if k != 1 then
				args[1] = args[1] .. " " .. v
			end
		end
	end
	local ok, mes = CAF.Construct(args[1])
	if ok then
		ply:ChatPrint("Addon Succesfully Enabled")
	else
		ply:ChatPrint("Couldn't Enable the Addon for the following reason: "..tostring(mes))
	end
end
concommand.Add( "CAF_Addon_Construct", AddonConstruct ) 

--[[
	This function will receive the destruct info from the clientside VGUI menu
]]
local function AddonDestruct(ply, com, args)
	if not ply:IsAdmin() then ply:ChatPrint("You are not allowed to Destruct a Custom Addon") return end
	if not args then ply:ChatPrint("You forgot to provide arguments") return end
	if not args[1] then ply:ChatPrint("You forgot to enter the Addon Name") return end
	if table.Count(args) > 1 then //Construct the Addon name if it had spaces in it
		for k , v in pairs(args) do
			if k != 1 then
				args[1] = args[1] .. " " .. v
			end
		end
	end
	local ok, mes = CAF.Destruct(args[1])
	if ok then
		ply:ChatPrint("Addon Succesfully Disabled")
	else
		ply:ChatPrint("Couldn't Disable the Addon for the following reason: "..tostring(mes))
	end
end
concommand.Add( "CAF_Addon_Destruct", AddonDestruct ) 

--[[
	This function will update the Client with all active addons
]]
function CAF.PlayerSpawn(ply)
	ply:ChatPrint("This server is using the Custom Addon Framework\n")
	ply:ChatPrint("Report any bugs during the beta on http://www.snakesvx.net\n")
	for k, v in pairs(Addons) do
		if v.GetStatus and v.GetStatus() then
			umsg.Start("CAF_Addon_Construct", ply)
				umsg.String(k)
			umsg.End()
		end
	end
end
hook.Add( "PlayerInitialSpawn", "CAF_In_Spawn", CAF.PlayerSpawn )


local oldcreate = ents.Create

ents.Create = function(class)
	local ent = oldcreate(class)
	timer.Simple( 0.1, OnEntitySpawn, ent, "SENT" )
	return ent;
end
--[[
	OnEntitySpawn
		This can be called by outside function to let the system know a Sent was spawned, since Sents spawned with an stool don't get detected by the SentSpawnHook
]]
--[[function CAF.OnEntitySpawn(ent , enttype , ply)
	OnEntitySpawn(ent , enttype , ply)
end]]

--[[
	This function can be called to register a Hook (similar to Hook.add)
	Possibly Hooks are:
		think: Just like the default Think Hook
		think2: Updates every 0.5 seconds
		think3: Updates every Second
		OnEntitySpawn: Get's called when an entity spawns (parameters: Entity, Type(string), Player who spawned it)
		OnAddonDestruct: Gets called when an addon gets disabled (Parameters: Name of addon)
		OnAddonConstruct: Get called when an addon gets Enabled (parameters: Name of addon)
		OnAddonExtraOptionChange: Gets called when an Extra option on an addon changes (Parameters: Addonname, CustomStatusname, NewStatus value)
		
]]
function CAF.AddHook(HookName, func)
	if  not HookName then return false , "No HookName given" end
	if not func then return false , "No function given" end
	if not hooks[HookName] then return false , "This hook doesn't exist" end
	table.insert(hooks[HookName], func)
		if(HookName == "think") then
		if(#hooks[HookName] == 1) then
			Msg("Adding CAF Think Hook");
			hook.Add("Think", "CAF Think", Think) 
		end
	elseif(HookName == "think2") then
		if(#hooks[HookName] == 1) then
			Msg("Adding CAF Think 2 Hook");
			timer.Create( "CAF Think 2", 0.5, 0, Think2 )
		end
	elseif(HookName == "think3") then
		if(#hooks[HookName] == 1) then
			Msg("Adding CAF Think 3 Hook");
			timer.Create( "CAF Think 3", 1, 0, Think3 )
		end
	end
	return true
end

--[[
	Remove the functions you added to a certain hook from here
]]
function CAF.RemoveHook(HookName, func)
	if  not HookName then return false , "No HookName given" end
	if not func then return false , "No function given" end
	if not hooks[HookName] then return false , "This hook doesn't exist" end
	for k, v in pairs(hooks[HookName]) do
		if (v == func) then
			table.remove(hooks[HookName], k)
		end
	end
	if(HookName == "think") then
		if(#hooks[HookName] == 0) then
			Msg("Removing CAF Think Hook");
			hook.Remove("Think", "CAF Think")
		end
	elseif(HookName == "think2") then
		if(#hooks[HookName] == 0) then
			Msg("Removing CAF Think 2 Hook");
			timer.Destroy("CAF Think 2")  
		end
	elseif(HookName == "think3") then
		if(#hooks[HookName] == 0) then
			Msg("Removing CAF Think 3 Hook");
			timer.Destroy("CAF Think 3")  
		end
	end
	return true
end

--[[
	Returns the Status of an extra option of an Addon or nil when not found
]]
function CAF.GetExtraOptionStatus(AddonName, OptionName)
	if not AddonName then return nil , "No AddonName given" end
	if not OptionName then return nil , "No OptionName given" end
	if( Addons[AddonName]) then
		if( Addons[AddonName].GetExtraOptions) then
			local tmp =  Addons[AddonName].GetExtraOptions()
			if (tmp) then
				if(tmp[OptionName]) then
					return tmp[OptionName].status
				end
				return nil , "This option wasn't found for this Addon"
			end
		end
		return nil , "No Extra options found for this Addon"
	end
	return nil , "Addon Not Found"	
end

--[[
	Returns the boolean status of an Addon
]]
function CAF.GetAddonStatus(AddonName)
	if not AddonName then return nil , "No AddonName given" end
	if( Addons[AddonName]) then
		local ok, status = pcall(Addons[AddonName].GetStatus)
		if(ok) then
			return status
		end
	end
	return nil , "No Status Info Found"
end

--[[
	Returns the custom status of the addon (if available)
]]
function CAF.GetAddonCustomStatus(AddonName)
	if not AddonName then return nil , "No AddonName given" end
	if( Addons[AddonName]) then
		local ok, status = pcall(Addons[AddonName].GetCustomStatus)
		if(ok) then
			return status
		end
	end
	return nil , "No Custom Status Info Found"
end

--[[
	Returns the Number and String version of this addon
]]
function CAF.GetAddonVersion(AddonName)
	if not AddonName then return nil , "No AddonName given" end
	if( Addons[AddonName]) then
		local ok, version, strver = pcall(Addons[AddonName].GetVersion)
		if(ok) then
			return version, strver
		end
	end
	return nil , "No Version Info Found"
end

--[[
	Returns the reference to the Custom Addon, nil if not existant
]]
function CAF.GetAddon(AddonName)
	if not AddonName then return nil , "No AddonName given" end
	return Addons[AddonName]
end

--[[
	Registers an addon with the game name into the table
	Overwrites if 2 addons use the same name
]]
function CAF.RegisterAddon(AddonName, AddonClass, level)
	if not AddonName then return nil , "No AddonName given" end
	if not AddonClass then return nil , "No AddonClass given" end
	if not level then level = "1" end
	Addons[AddonName] = AddonClass
	table.insert(addonlevel[level], AddonName)
	return  true
end

--[[
	The following code sends the clientside and shared files to the client and includes CAF core code
]]
//Send Client and Shared files to the client and Include the ServerAddons

//Core files

local Files = file.FindInLua( "caf_core/server/*.lua" )
for k, File in ipairs(Files) do
	Msg("Loading: "..File.."...")
	local ErrorCheck, PCallError = pcall(include, "caf_core/server/"..File)
	if(!ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Loaded: Successfully\n")
	end
end

Files = file.FindInLua("caf_core/client/*.lua")
for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf_core/client/"..File)
	if(!ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end

Files = file.FindInLua("caf_core/shared/*.lua")
for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf_core/shared/"..File)
	if(!ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end

//Main Addon
local Files = file.FindInLua( "caf_addons/server/*.lua" )
for k, File in ipairs(Files) do
	Msg("Loading: "..File.."...")
	local ErrorCheck, PCallError = pcall(include, "caf_addons/server/"..File)
	if(!ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Loaded: Successfully\n")
	end
end

Files = file.FindInLua("caf_addons/client/*.lua")
for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf_addons/client/"..File)
	if(!ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end

Files = file.FindInLua("caf_addons/shared/*.lua")
for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf_addons/shared/"..File)
	if(!ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end

//Extra files

Files = file.FindInLua("caf_extra_addon_files/client/*.lua")
for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf_extra_addon_files/client/"..File)
	if(!ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end

Files = file.FindInLua("caf extra addon files/shared/*.lua")
for k, File in ipairs(Files) do
	Msg("Loading: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf_extra_addon_files/shared/"..File)
	if(!ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end
